package main

import "fmt"

func main() {

	//从数组或切片生成新的切片
	fmt.Println("从数组或切片生成新的切片")
	{
		var highRiseBuilding [30]int
		for i := 0; i < 30; i++ {
			highRiseBuilding[i] = i + 1
		}

		// 区间
		fmt.Println(highRiseBuilding[10:15])

		// 中间到尾部的所有元素
		fmt.Println(highRiseBuilding[20:])

		// 开头到指定元素
		fmt.Println(highRiseBuilding[:2])

		// 原有
		a := []int{1, 2, 3}
		fmt.Println(a[:])

		// 重置切片
		fmt.Println(a[0:0])
	}

	// 直接声明新的切片
	fmt.Println("直接声明新的切片")
	{
		// 声明字符串切片
		var strList []string

		// 声明整型切片
		var numList []int

		// 声明一个空切片
		var numListEmpty = []int{}

		fmt.Println(strList, numList, numListEmpty)

		fmt.Println(strList == nil)
		fmt.Println(numList == nil)
		fmt.Println(numListEmpty == nil)
	}

	// 使用make()函数构造切片
	fmt.Println("使用make()函数构造切片")
	{
		// make( []Type, size, cap )
		a := make([]int, 2)
		b := make([]int, 2, 10)
		fmt.Println(a, b)
		fmt.Println(len(a), len(b))
	}

	// 使用append()为切片添加元素
	fmt.Println("使用append()为切片添加元素")
	{
		var a []int
		a = append(a, 1)
		a = append(a, 1, 2, 3)
		fmt.Println(a)
		a = append(a, []int{1, 2, 3}...)
		fmt.Println(a)

		//不过需要注意的是，在使用 append() 函数为切片动态添加元素时，如果空间不足以容纳足够多的元素，切片就会进行“扩容”，此时新切片的长度会发生改变。
		//切片在扩容时，容量的扩展规律是按容量的 2 倍数进行扩充，例如 1、2、4、8、16……，代码如下：
		//扩充的时候，该Slice的内存地址会发生变化
		var numbers []int
		for i := 0; i < 65; i++ {
			numbers = append(numbers, i)
			fmt.Printf("len: %d  cap: %d pointer: %p\n", len(numbers), cap(numbers), numbers)
		}

		//除了在切片的尾部追加，我们还可以在切片的开头添加元素，在切片开头添加元素一般都会导致内存的重新分配，而且会导致已有元素全部被复制 1 次，
		//因此，从切片的开头添加元素的性能要比从尾部追加元素的性能差很多。
		var b = []int{1, 2, 3}
		b = append([]int{0}, b...)
		b = append([]int{-3, -2, -1}, b...)
		fmt.Println(b)

		// 因为 append 函数返回新切片的特性，所以切片也支持链式操作，我们可以将多个 append 操作组合起来，实现在切片中间插入元素
		//var c []int
		//c = append(c[:i], append([]int{x}, c[i:]...)...) // 在第i个位置插入x
		//c = append(c[:i], append([]int{1,2,3}, c[i:]...)...) // 在第i个位置插入切片
	}

	// copy() 切片复制，切片拷贝
	// copy( destSlice, srcSlice []T) int
	fmt.Println("copy() 切片复制，切片拷贝")
	{
		slice1 := []int{1, 2, 3, 4, 5}
		slice2 := []int{5, 4, 3}
		copy(slice2, slice1)
		fmt.Println(slice2)
		slice2 = []int{5, 4, 3}
		copy(slice1, slice2)
		fmt.Println(slice1)
	}

	// 通过代码演示对切片的引用和复制操作后对切片元素的影响。
	fmt.Println("通过代码演示对切片的引用和复制操作后对切片元素的影响。")
	{
		// 设置元素数量为1000
		const elementCount = 1000
		// 预分配足够多的元素切片
		srcData := make([]int, elementCount)
		// 将切片赋值
		for i := 0; i < elementCount; i++ {
			srcData[i] = i
		}
		// 引用切片数据
		refData := srcData
		// 预分配足够多的元素切片
		copyData := make([]int, elementCount)
		// 将数据复制到新的切片空间中
		copy(copyData, srcData)
		// 修改原始数据的第一个元素
		srcData[0] = 999 // 打印引用切片的第一个元素
		fmt.Println(refData[0])
		// 打印复制切片的第一个和最后一个元素
		fmt.Println(copyData[0], copyData[elementCount-1])
		// 复制原始数据从4到6(不包含)
		copy(copyData, srcData[4:6])
		for i := 0; i < 5; i++ {
			fmt.Printf("%d ", copyData[i])
		}
	}

	// Go语言从切片中删除元素
	fmt.Println("Go语言从切片中删除元素")
	{
		//从开头位置删除
		fmt.Println("从开头位置删除")
		{
			//删除开头的元素可以直接移动数据指针：
			a := []int{1, 2, 3}
			fmt.Printf("a start %v %p \n", a, a)
			a = a[1:]
			//a=a[N:]
			fmt.Printf("a end %v %p \n", a, a)

			//也可以不移动数据指针，但是将后面的数据向开头移动，可以用append原地完成（所谓原地完成时指在原有的切片数据对应的内存区间内完成，不会导致内存空间结构的变化（））：
			b := []int{1, 2, 3}
			fmt.Printf("b start %v %p \n", b, b)
			b = append(b[:0], b[1:]...) // 删除开头1个元素
			//a = append(a[:0], a[N:]...) // 删除开头N个元素
			fmt.Printf("b end %v %p \n", b, b)

			// 还可以用 copy() 函数来删除开头的元素：
			c := []int{1, 2, 3}
			fmt.Printf("c start %v %p \n", c, c)
			//cLen := copy(c, c[1:])
			//fmt.Printf("cLen value %v \n", cLen)
			c = c[:copy(c, c[1:])]
			fmt.Printf("c end %v %p \n", c, c)
		}

		//从中间位置删除
		fmt.Println("从中间位置删除")
		{
			a := []int{1, 2, 3, 4}
			fmt.Printf("a start %v %p \n", a, a)
			a = append(a[:1], a[2:]...)
			fmt.Printf("a start %v %p \n", a, a)

			b := []int{1, 2, 3, 4}
			fmt.Printf("b start %v %p \n", b, b)
			i := 1
			n := 2
			b = b[:i+copy(b[i:], b[i+n:])]
			fmt.Printf("b end %v %p \n", b, b)
			//
		}

		//从尾部删除
		fmt.Println("从尾部删除")
		{
			a := []int{1, 2, 3}
			fmt.Printf("a start %v %p \n", a, a)
			a = a[:len(a)-1]
			fmt.Printf("a start %v %p \n", a, a)
		}
	}
}
